// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{fs::Permissions, os::unix::fs::PermissionsExt, path::{Path, PathBuf}};

use log::error;

#[cfg(not(target_pointer_width = "64"))]
const FHS_NORMAL_DIRS: [&str; 5] = ["etc", "var", "usr/bin", "usr/lib", "usr/sbin"];
#[cfg(target_pointer_width = "64")]
const FHS_NORMAL_DIRS: [&str; 6] = ["etc", "var", "usr/bin", "usr/lib", "usr/lib64", "usr/sbin"];

#[cfg(not(target_pointer_width = "64"))]
pub const FHS_USR_LINK_DIRS: [&str; 3] = ["bin", "lib", "sbin"];
#[cfg(target_pointer_width = "64")]
pub const FHS_USR_LINK_DIRS: [&str; 4] = ["bin", "lib", "lib64", "sbin"];

#[derive(Debug, thiserror::Error)]
pub enum FhsBuildError {
    #[error("failed to create directory '{1}'")]
    DirCreateFailed(#[source] tokio::io::Error, PathBuf),
    #[error("'{0}' already exists but it is a file")]
    FileAlreadyExists(PathBuf),
    #[error("failed to set permissions of '{1}'")]
    SetPermissionsFailed(#[source] tokio::io::Error, PathBuf),
}

pub async fn build_fhs<P: AsRef<Path>>(root: P) -> Result<(), FhsBuildError> {
    let root = root.as_ref();

    for dir in FHS_NORMAL_DIRS {
        create_fhs_dir(&root.join(dir)).await?;
    }

    for dir in FHS_USR_LINK_DIRS {
        // The src path is relative to '/' for chroot
        let src = PathBuf::from_iter(["usr", dir]);
        let dst = root.join(dir);
        if dst.exists() {
            if !dst.is_dir() {
                return Err(FhsBuildError::FileAlreadyExists(dst));
            }
        } else {
            tokio::fs::symlink(&src, &dst)
                .await
                .map_err(|err| FhsBuildError::DirCreateFailed(err, dst))?;
        }
    }

    let tmp_dir_path = root.join("tmp");
    if tmp_dir_path.exists() {
        if !tmp_dir_path.is_dir() {
            return Err(FhsBuildError::FileAlreadyExists(tmp_dir_path));
        }
    } else {
        create_fhs_dir(&tmp_dir_path).await?;
        tokio::fs::set_permissions(&tmp_dir_path, Permissions::from_mode(0o1777))
            .await
            .map_err(|err| FhsBuildError::SetPermissionsFailed(err, tmp_dir_path))?;
    }
    
    Ok(())
}

pub async fn clear_fhs<P: AsRef<Path>>(root: P) -> bool {
    const DIRS: [&str; 5] = ["etc", "var", "usr", "tmp", "host"];

    let root = root.as_ref();
    let mut success = true;

    for dir in DIRS {
        let path = root.join(dir);
        if path.exists() {
            if let Err(err) = tokio::fs::remove_dir_all(&path).await {
                error!("failed to remove directory '{}': {}", path.display(), err);
                success = false;
            }
        }
    }

    for link in FHS_USR_LINK_DIRS {
        let path = root.join(link);
        if path.exists() {
            if let Err(err) = tokio::fs::remove_file(&path).await {
                error!("failed to remove symlink '{}': {}", path.display(), err);
                success = false;
            }
        }
    }

    success
}

async fn create_fhs_dir(path: &Path) -> Result<(), FhsBuildError> {
    if path.exists() {
        if !path.is_dir() {
            return Err(FhsBuildError::FileAlreadyExists(path.to_path_buf()));
        }
    } else {
        tokio::fs::create_dir_all(&path)
            .await
            .map_err(|err| FhsBuildError::DirCreateFailed(err, path.to_path_buf()))?;
    }
    Ok(())
}
